#include <QUuid>
#include <QtTest/QtTest>
#include "insim.h"
using namespace LFS;

/**************************************
 * T E S T  C L A S S  D E F I N I T I O N
 */
class TestInSim: public QObject
{
Q_OBJECT

public:
    /**
     * Constructor.
     */
    TestInSim()
    {
        lfs = NULL;
        testPort = 30003;
        testHostname = "localhost";
        testClientName = QUuid::createUuid().toString().mid(1,16);
        expectingError = false;
        waitIsActive=true;
    }

private slots:
    //! Test initializer loads the InSim plugin.
    void initTestCase();

    /**
     * Test the member variable lfs; initTestCase()
     * set it up, but didn't try its validity.
     */
    void testInSimPointerValidity();

    //! Test connecting the signals.
    void testConnectSignalsAndSlots();

    //! Connect to the stored host.
    void testConnectToHostStored();

    //! Connect to the specified host.
    void testConnectToHost();

    //! Test connection data.
    void testConnectionDataAccessors();

    //! Test data packet reception.
    void testDataPacketReception();

    //! Disconnect the current connection.
    void cleanupTestCase();

    //! Catch signals sent by the plugin.
public slots:
    //! An error is signalled with this string.
    void insimErrorString(QString &errorString);

    //! Propagate socket connection to this class' client with this signal.
    void insimConnected();

    //! Propagate socket disconnection to this class' client with this signal.
    void insimDisconnected();

private:
    //! Wait for a while.
    void wait(int delay);

    //! Check the provided directory.
    bool checkDirectoryForPlugin(QDir &candidate);

    //! Plugin loader.
    bool loadPlugin();

    //! Data.
    InSim *lfs;
    QString hostname;
    QString clientName;
    quint16 port;
    quint16 testPort;
    QString testHostname;
    QString testClientName;
    bool expectingError;
    bool waitIsActive;
};

/**************************************
 * S U P P O R T  F U N C T I O N S
 */

//! Check the provided directory.
bool TestInSim::checkDirectoryForPlugin(QDir &candidate)
{
    //! Check for 'plugins'-directory.
    foreach(QString dirName, candidate.entryList(QDir::Dirs))
        {
            if( 0 == dirName.compare( "plugins", Qt::CaseInsensitive ) )
            {
                return true;
            }
        }
    return false;
}

//! Plugin loader.
bool TestInSim::loadPlugin()
{
    /**
     * Loop through directories, traversing up the tree.
     * Quit when plugins-dir is found or we're at the
     * root directory.
     */
    QDir pluginsDir = qApp->applicationDirPath();
    do
    {
        if( pluginsDir.isRoot() )
        {
            return false;
        }
        else
        {
            if( false == pluginsDir.cdUp() )
            {
                return false;
            }
        }
    } while( false == checkDirectoryForPlugin( pluginsDir ) );
    /**
     * Once we're out the loop above, we're in a directory
     * which contains a plugins-directory.
     */
    pluginsDir.cd( "plugins" );
    //! Load the plugin.
    foreach(QString fileName, pluginsDir.entryList(QDir::Files))
        {
            QString absoluteFilePath = pluginsDir.absoluteFilePath( fileName );
            QPluginLoader pluginLoader( absoluteFilePath );
            QObject *plugin = pluginLoader.instance();
            if( plugin )
            {
                LFS::InSimInterface *lfsInterface = qobject_cast<LFS::InSimInterface *> ( plugin );
                if( lfsInterface != NULL )
                {
                    lfs = lfsInterface->getInSim();
                    QString fmt = "Using '%1'.";
                    qDebug( fmt.arg( absoluteFilePath ).toAscii() );
                    return true;
                }
            }
        }
    return false;
}

//! Wait for a while.
void TestInSim::wait(int delay)
{
    const int loopLimit=40;
    qDebug("Waiting...");
    waitIsActive=true;
    for(int loopCounter=0;waitIsActive&&(loopCounter<loopLimit);loopCounter++)
    {
        QTest::qWait(delay/loopLimit);
    }
    if(waitIsActive)
    {
        qDebug("Wait timed out.");
    }
    else
    {
        qDebug("Wait is over.");
    }
}

/**************************************
 * F R A M E W O R K  S L O T S
 */

//! Tests are initialized by loading the plugin.
void TestInSim::initTestCase()
{
    //! Load the plugin.
    bool loadResult = loadPlugin();
    QCOMPARE(loadResult, true);
}

//! Disconnect the current connection.
void TestInSim::cleanupTestCase()
{
    //! Disconnect and test connection: it should be false after disconnection.
    lfs->disconnectFromHost();
    wait(5000);
    bool retVal = lfs->isConnected();
    QCOMPARE(retVal, false);
}

/**************************************
 * P R I V A T E  T E S T  S L O T S
 */

//! Check the InSim plugin pointer.
void TestInSim::testInSimPointerValidity()
{
    //! Plugin loader set up variable lfs; check it.
    QVERIFY(lfs!=NULL);
}

//! Test connecting the signals.
void TestInSim::testConnectSignalsAndSlots()
{
    bool successValue = connect( lfs, SIGNAL(insimErrorString(QString &)), this, SLOT(insimErrorString(QString &)) );
    QCOMPARE(successValue, true);
    successValue = connect( lfs, SIGNAL(insimConnected()), this, SLOT(insimConnected()) );
    QCOMPARE(successValue, true);
    successValue = connect( lfs, SIGNAL(insimDisconnected()), this, SLOT(insimDisconnected()) );
    QCOMPARE(successValue, true);
}

//! Test connection data.
void TestInSim::testConnectionDataAccessors()
{
    expectingError=false;
    lfs->setConnectionData( testClientName, testHostname, testPort );
    lfs->getConnectionData( clientName, hostname, port );
    QCOMPARE(clientName, testClientName);
    QCOMPARE(hostname, testHostname);
    QCOMPARE(port, testPort);
}

//! Connect to the specified host.
void TestInSim::testConnectToHost()
{
    expectingError=false;
    //! Set host data, connect to it and check status.
    lfs->connectToHost( testClientName, testHostname, testPort );
    //! Sleep for 10 seconds to wait for socket state to change.
    wait(5000);
    bool retVal = lfs->isConnected();
    QCOMPARE(retVal, true);
}

//! Connect to the stored host without setting the data.
void TestInSim::testConnectToHostStored()
{
    //! Try to connect.
    expectingError=true;
    lfs->connectToHost();
    bool retVal = lfs->isConnected();
    QCOMPARE(retVal, false);
}

//! Wait for data to appear.
void TestInSim::testDataPacketReception()
{
    wait(90000);
}


/**************************************
 * I N S I M  S I G N A L L I N G  S L O T S
 */

//! An error is signalled with this string.
void TestInSim::insimErrorString(QString &errorString)
{
    if(expectingError)
    {
        errorString.prepend("Expected error: ");
    }
    else
    {
        errorString.prepend("Unexpected error: ");
    }
    qDebug(errorString.toAscii());
    if(!expectingError)
    {
        //! Unexpected error: bail out.
        QVERIFY(expectingError);
    }
}

//! Propagate socket connection to this class' client with this signal.
void TestInSim::insimConnected()
{
    qDebug("insimConnected");
    //! All connect/disconnect tests set the waitIsActive -flag. Reset it.
    waitIsActive=false;
}

//! Propagate socket disconnection to this class' client with this signal.
void TestInSim::insimDisconnected()
{
    qDebug("insimDisconnected");
    //! All connect/disconnect tests set the waitIsActive -flag. Reset it.
    waitIsActive=false;
}


/**************************************
 * B O I L E R P L A T E  C O D E
 */
//! Generate main routine.
QTEST_MAIN(TestInSim)
#include "testinsim.moc"
